package assembly;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.util.Scanner;
import simulation.ILoadable;
import simulation.SpringiesConstants;
import util.Location;
import util.Pixmap;
import util.Sprite;
import util.Vector;


/**
 * Black Circle that represents a mass
 * 
 * @author Robert C. Duvall
 * @author Dagbedji Fagnisse
 * @author Scott Valentine
 */

public class Mass extends Sprite {
    /**
     * the default dimensions for use of the mass image
     */
    public static final Dimension DEFAULT_SIZE = new Dimension(16, 16);

    /**
     * the default image to use when using the image for a mass
     */
    public static final Pixmap DEFAULT_IMAGE = new Pixmap("mass.gif");

    /**
     * the default starting x coordinate of the mass center
     */
    public static final double DEFAULT_X = 200.0;

    /**
     * the default starting y coordinate of the mass center
     */
    public static final double DEFAULT_Y = 200.0;

    /**
     * the default mass value for the mass object
     */
    public static final double DEFAULT_MASS = 10.0;

    private static final String KEYWORD = SpringiesConstants.MASS_KEYWORD;

    private int myID;
    private double myMass;
    private Vector myAcceleration;
    private Assembly myAssembly;

    /**
     * generalized constructor that depends on location (x, y) and the mass value
     * 
     * @param x coordinate of the start position
     * @param y coordinate of the start position
     * @param mass value for this object
     */
    public Mass (double x, double y, double mass) {
        super(DEFAULT_IMAGE, new Location(x, y), DEFAULT_SIZE);
        myMass = mass;
        myAcceleration = new Vector();
    }

    /**
     * creates a mass at the given coordinates, the given weight, and contained in the given
     * assembly
     * 
     * @param x - xpoistion to add the mass at
     * @param y - yposition to add the mass at
     * @param mass - weight of mass to be added
     * @param a - assembly in which the mass is located
     */
    public Mass (double x, double y, double mass, Assembly a) {
        this(x, y, mass);
        myAssembly = a;
    }

    /**
     * creates mass based on point and mass
     * 
     * @param p - point where mass is added
     * @param mass - weight of mass to be added
     */
    public Mass (Point2D p, double mass) {
        super(DEFAULT_IMAGE, new Location(p), DEFAULT_SIZE);
        myMass = mass;
        myAcceleration = new Vector();
    }

    /**
     * Main Constructor for a Mass. The ID field is mandatory.
     * 
     * @param id of the mass
     * @param x coordinate of the mass
     * @param y coordinate of the mass
     * @param mass - the 'weight'
     */
    public Mass (int id, double x, double y, double mass) {
        this(x, y, mass);
        myID = id;
    }

    /**
     * Default Constructor for a mass. Should be used only for dumb samples.
     */
    public Mass () {
        this(DEFAULT_X, DEFAULT_Y, DEFAULT_MASS);
    }

    /**
     * gives the mass value
     * 
     * @return mass value
     */
    public double getMass () {
        return myMass;
    }

    /**
     * gives the id of this mass, used for getting specific masses for spring construction
     * 
     * @return id of mass
     */
    public int getID () {
        return myID;
    }

    /**
     * gives the assembly in which this mass is located
     * 
     * @return this mass' assembly
     */
    public Assembly getAssembly () {
        return myAssembly;
    }

    @Override
    public void update (double elapsedTime, Dimension bounds) {
        // bounce force is a special case.
        applyForce(getBounce(bounds));
        super.update(elapsedTime, bounds);
    }

    @Override
    public void paint (Graphics2D pen) {
        pen.setColor(Color.BLACK);
        pen.fillOval((int) getLeft(), (int) getTop(), (int) getWidth(),
                     (int) getHeight());
    }

    /**
     * applies the given force to this mass and updates the velocity accordingly
     * 
     * @param force that is applied to the mass
     */
    public void applyForce (Vector force) {
        myAcceleration.sum(force);
        getVelocity().sum(myAcceleration);
        myAcceleration.reset();
    }

    // check for move out of bounds
    private Vector getBounce (Dimension bounds) {
        final double IMPULSE_MAGNITUDE = 2;
        Vector impulse = new Vector();
        if (getLeft() < 0) {
            impulse = new Vector(RIGHT_DIRECTION, IMPULSE_MAGNITUDE);
        }
        else if (getRight() > bounds.width) {
            impulse = new Vector(LEFT_DIRECTION, IMPULSE_MAGNITUDE);
        }
        if (getTop() < 0) {
            impulse = new Vector(DOWN_DIRECTION, IMPULSE_MAGNITUDE);
        }
        else if (getBottom() > bounds.height) {
            impulse = new Vector(UP_DIRECTION, IMPULSE_MAGNITUDE);
        }
        impulse.scale(getVelocity().getRelativeMagnitude(impulse));
        return impulse;
    }

    @Override
    public ILoadable load (Scanner line, Assembly a) {
        double x = line.nextDouble();
        double y = line.nextDouble();
        double mass = line.nextDouble();
        Mass result = (mass < 0) ? new HeavyMass(x, y, mass, a) : new Mass(x, y, mass, a);
        return result;
    }

    /**
     * returns the distance between two masses
     * 
     * @param myEnd - the other mass
     * @return distance between this mass and myEnd
     */
    public double distance (Mass myEnd) {
        double x1 = this.getX();
        double y1 = this.getY();

        double x2 = myEnd.getX();
        double y2 = myEnd.getY();

        double dx = x2 - x1;
        double dy = y2 - y1;

        return Math.sqrt(dx * dx + dy * dy);
    }

    @Override
    public String getKeyword () {
        return KEYWORD;
    }

}
